package com.qfedu.edu.seckill.orderservice.slot.impl;

import com.qfedu.edu.seckill.constance.RedisConstance;
import com.qfedu.edu.seckill.exception.BusinessException;
import com.qfedu.edu.seckill.manager.RedisManager;
import com.qfedu.edu.seckill.orderservice.slot.generic.GenericSlot;
import com.qfedu.edu.seckill.result.R;
import com.qfedu.edu.seckill.result.ResponseCode;
import com.qfedu.edu.seckill.utils.HttpResponseUtils;
import com.qfedu.edu.seckill.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaobobo
 * @title: LimitSlot
 * @projectName cd-java-2404-seckill-parent
 * @description: TODO
 * @date 2025/2/6  9:44
 */
@Component
@Slf4j
public class LimitSlot extends GenericSlot {

    @Autowired
    private RedisManager redisManager;

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE + 1;
    }

    @Override
    public void dealRequest(HttpServletRequest request, HttpServletResponse response) {
        log.info("BlackInterceptor执行了....");
        /**
         * 攻击的限定
         *    1、1分钟之内请求20次以上算攻击
         *    2、1小时请求200次以上算攻击
         *    3、1天之内请求超过20000次算攻击
         *    这里要使用Redis中的这个SortedSet来解决这个问题
         *     1、添加一条数据到ZSet中 有那些参数
         *        1、集合的名字    得分    集合的元素  最终会按照这个得分进行从小到大的排序
         *     那么这里我们如何设计呢?
         *     req:ip地址   当前的请求的时间戳   随机值
         *
         *     用户每一次请求 都记录一条数据
         *
         *     在Zset中 Count(得分区间内成员的个数)
         *
         */
        //把ip地址拿到
        String ipAddress = IpUtils.getIpAddress(request);
        //构建ZSet中集合的名字
        String limitKey = RedisConstance.getReqLimitPrefix() + ipAddress;
        //获取当前时间
        long currentTime = DateTime.now().getMillis();
        //计算一分钟之前的时间
        long minuteAgo = DateTime.now().minusMinutes(1).getMillis();
        //计算一小时之间的时间
        long hoursAgo = DateTime.now().minusHours(1).getMillis();
        //计算一天之前的时间
        long dayAgo = DateTime.now().minusDays(1).getMillis();
        //通过调用ZSet中得分区间内成员个数统计的API来完成这个数据的判断
        if (redisManager.countZSetByScore(limitKey, minuteAgo, currentTime) >= 20) {
            log.info("超过了1分钟限流");
            addBlack(ipAddress);
            throw new BusinessException(ResponseCode.MINUTE_LIMIT_EXCEPTION);
        }
        if (redisManager.countZSetByScore(limitKey, hoursAgo, currentTime) >= 20) {
            log.info("超过了1小时限流");
            addBlack(ipAddress);
            //那么说明是攻击
            throw new BusinessException(ResponseCode.HOUSE_LIMIT_EXCEPTION);
        }
        if (redisManager.countZSetByScore(limitKey, dayAgo, currentTime) >= 20) {
            log.info("超过了1天限流");
            addBlack(ipAddress);
            throw new BusinessException(ResponseCode.DAY_LIMIT_EXCEPTION);
        }
        //执行到这里 说明不是攻击 那么就要向Redis中写入记录
        redisManager.addZSet(limitKey, currentTime, UUID.randomUUID().toString());
        log.info("限流校验通过");
    }


    /**
     * 添加黑名单
     *
     * @param ipAddress
     */
    private void addBlack(String ipAddress) {
        //还需要记录还名单
        //那么黑名单如何设计呢?
        // 使用Redis中的 String类型   key=black:ip  val：随机数
        //构建key
        String blackKey = RedisConstance.getReqBlackPrefix() + ipAddress;
        //构建val
        String blackVal = UUID.randomUUID().toString();
        //添加到Redis中
        redisManager.addString(blackKey, blackVal, 1, TimeUnit.DAYS);
    }
}
